﻿using System;
using System.Runtime.InteropServices;
using SCG = System.Collections.Generic;

using Nemerle.Assertions;
using Nemerle.Utility;

using GASS.OpenCL;
using GASS.OpenCL.OpenCLDriver;

namespace Nemerle.OpenCL {

    /// <summary>
    /// Description of Context.
    /// </summary>
    [InfoProperty(CLContextInfo.ReferenceCount, int, Flags(no_cache))]
    [EqualityByIdPattern]
    public sealed class Context : IDisposable {

        public this() {
            this(array[ContextCreateInfo(Device.DefaultDevice)])
        }

        public this(device : Device) {
            this(array[ContextCreateInfo(device)])
        }

        public this(devices : array[Device]) {
            this(Array.ConvertAll(devices, ContextCreateInfo(_)))
        }

        public this([NotNull] context_create_infos : array[ContextCreateInfo])
            requires context_create_infos.Length > 0
        {
            def ids = Array.ConvertAll(context_create_infos, info => info.Device.Id);
            mutable props;
            this.handle = ContextHandle(chk_err_t clCreateContext(ref props, ids.Length :> uint, ids, null, IntPtr.Zero));

            this.devices = Array.AsReadOnly(Array.ConvertAll(context_create_infos, _.Device));
            this.device = this.devices[0];
            this.queues = Array.AsReadOnly(Array.ConvertAll(context_create_infos, info => CommandQueue(this, info)));
            this.queue = this.queues[0];
            this.previous_context = current_context;
            current_context = this;
        }

        previous_context : Context;

        handle : ContextHandle;

        public Id : CLContext {
            get { handle.EntityId }
        }

        [Accessor] devices : System.Collections.ObjectModel.ReadOnlyCollection[Device];
        [Accessor] device : Device;

        [Accessor] queues : System.Collections.ObjectModel.ReadOnlyCollection[CommandQueue];
        [Accessor] queue : CommandQueue;

        public BuildProgram(source : string, options : ProgramBuildOptions) : Program {
            Program(this, source, options)
        }

        public BuildProgram(source : string) : Program {
            Program(this, source, ProgramBuildOptions(devices))
        }

        public static UnloadCompiler() : void {
            CheckError(clUnloadCompiler());
        }

        #region Buffer create utils

        public CreateBuffer(size : int) : Nemerle.OpenCL.Buffer {
            Buffer(this, size)
        }

        public CreateBuffer(size : int, flags : CLMemFlags) : Nemerle.OpenCL.Buffer {
            Buffer(this, size, flags)
        }

        public CreateBufferWithData[T](buffer : array[T]) : Nemerle.OpenCL.Buffer {
            Buffer(this, Utils.ArrayHelper.GetArraySize.[T](buffer), buffer)
        }

        public CreateBufferWithData[T](buffer : array[T], flags : CLMemFlags) : Nemerle.OpenCL.Buffer {
            Buffer(this, Utils.ArrayHelper.GetArraySize.[T](buffer), buffer, flags)
        }

        public CreateBufferWithData[T](buffer : array[2, T]) : Nemerle.OpenCL.Buffer {
            Buffer(this, Utils.ArrayHelper.GetArraySize.[T](buffer), buffer)
        }

        public CreateBufferWithData[T](buffer : array[2, T], flags : CLMemFlags) : Nemerle.OpenCL.Buffer {
            Buffer(this, Utils.ArrayHelper.GetArraySize.[T](buffer), buffer, flags)
        }

        public CreateBufferWithData[T](buffer : array[3, T]) : Nemerle.OpenCL.Buffer {
            Buffer(this, Utils.ArrayHelper.GetArraySize.[T](buffer), buffer)
        }

        public CreateBufferWithData[T](buffer : array[3, T], flags : CLMemFlags) : Nemerle.OpenCL.Buffer {
            Buffer(this, Utils.ArrayHelper.GetArraySize.[T](buffer), buffer, flags)
        }

        public CreateBufferWithData[T](obj : T) : Nemerle.OpenCL.Buffer where T : struct {
            Buffer(this, Marshal.SizeOf(typeof(T)), obj)
        }

        public CreateBufferWithData[T](obj : T, flags : CLMemFlags) : Nemerle.OpenCL.Buffer where T : struct {
            Buffer(this, Marshal.SizeOf(typeof(T)), obj, flags)
        }

        #endregion

        #region Image utils

        public GetSupportedImageFormats2D(flags = CLMemFlags.ReadWrite) : array[CLImageFormat] {
            buffered clGetSupportedImageFormats(this.Id, flags, CLMemObjectType.Image2D)
        }

        public GetSupportedImageFormats3D(flags = CLMemFlags.ReadWrite) : array[CLImageFormat] {
            buffered clGetSupportedImageFormats(this.Id, flags, CLMemObjectType.Image3D)
        }

        #endregion

        #region IDisposable

        public Dispose() : void {
            when(ReferenceEquals(current_context, this)) {
                current_context = previous_context;
                when(null != queues)
                    foreach(queue in queues)
                        queue.Dispose();
                when(null != handle)
                    handle.Close();
            }
        }

        #endregion

        [ThreadStatic] static mutable current_context : Context;

        public static CurrentContext : Context {
            get { current_context }
        }

    }

}
